/* Copyright Thanh-Trung Pham, JAIST, 2013 */
package jp.ac.jaist.computation;

import com.google.common.collect.Sets;
import jp.ac.jaist.*;
import jp.ac.jaist.BranchingStructureType.IfPart;
import jp.ac.jaist.MultiTryCatchStructureType.MultiTryCatchPart;
import jp.ac.jaist.input.*;
import jp.ac.jaist.model.markov.RMPIMarkovModel;
import jp.ac.jaist.model.markov.RMPIMarkovState;
import org.apache.commons.collections.keyvalue.MultiKey;
import org.apache.commons.collections.map.MultiKeyMap;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

/**
 * @author Thanh-Trung Pham
 */
public class RMPIReliabilityCalculator {

    private static final String RMPI_DOT = ".";
    private static final String RMPI_COMMA = ",";
    private static final String RMPI_ALL = "ALL";
    private static final String RMPI_NONE = "NONE";
    private UsageProfilePartType m_oUsageProfilePart;
    private Map<String, RMPIIAWrapper> m_oTransformedProvidedServices;
    private RMPIInputModeler m_oInputModel;

    /**
     * The constructor.
     * @param inputModel the input model.
     */
    private RMPIReliabilityCalculator(RMPIInputModeler inputModel) {
        m_oInputModel = inputModel;
        m_oTransformedProvidedServices = new HashMap<String, RMPIIAWrapper>();
    }

    /**
     * Create a new instance.
     * @param inputModel the input model.
     * @return the newly created instance.
     */
    public static RMPIReliabilityCalculator newInstance(RMPIInputModeler inputModel) {
        return new RMPIReliabilityCalculator(inputModel);
    }

    /**
     * Get all the transformed provided services.
     *
     * @return all the transformed provided services.
     */
    public Map<String, RMPIIAWrapper> getTransformedProvidedServices() {
        return m_oTransformedProvidedServices;
    }

    /**
     * Clear all the cached data.
     */
    private void clearCachedData() {
        m_oUsageProfilePart = null;
        m_oTransformedProvidedServices.clear();
    }

    /**
     * Calculate the reliability-related probabilities for the input model.
     *
     * @return an equivalent internal activity with the reliability-related probabilities.
     */
    public synchronized RMPIIAWrapper calculateReliabilityRelatedProbabilities() {
        UsageProfileType oUsageProfile = m_oInputModel.getUsageProfile();

        Set<Set<String>> oAFS = m_oInputModel.getAFS();
        Set<Set<String>> oAIOS = m_oInputModel.getAIOS();

        RMPIIAWrapper oReturnIAWrapper = new RMPIIAWrapper(m_oInputModel, true);
        UsageProfilePartType[] oUsageProfileParts = oUsageProfile.getUsageProfilePartArray();

        for (UsageProfilePartType oUsageProfilePart : oUsageProfileParts) {
            clearCachedData();

            m_oUsageProfilePart = oUsageProfilePart;
            double dUsageProfilePartProbability = oUsageProfilePart.getProbability();

            UserInterfaceType oUserInterface = m_oInputModel.getUserInterfaceByName(m_oUsageProfilePart.getForUserInterface());
            String strComponentInstanceName = oUserInterface.getFromService().getFromComponentInstance();
            String strProvidedServiceName = oUserInterface.getFromService().getFromProvidedService();

            RMPIIAWrapper oEquivalentIAWrapper = transformIntoEquivalentIA(strComponentInstanceName, strProvidedServiceName);

            for (Set<String> oI : oAIOS) {
                for (Set<String> oF : oAFS) {
                    oReturnIAWrapper.setProbability(oI, oF, oReturnIAWrapper.getProbability(oI, oF) + dUsageProfilePartProbability * oEquivalentIAWrapper.getProbability(oI, oF));
                }
                for (Set<String> oO : oAIOS) {
                    oReturnIAWrapper.setProbability(oI, oO, oReturnIAWrapper.getProbability(oI, oO) + dUsageProfilePartProbability * oEquivalentIAWrapper.getProbability(oI, oO));
                }
            }
        }

        return oReturnIAWrapper;
    }

    /**
     * Transform a provided service implementation into an equivalent internal activity.
     *
     * @param componentInstanceName the current component instance name.
     * @param providedServiceName   a provide service name.
     * @return the equivalent internal activity.
     */
    private RMPIIAWrapper transformIntoEquivalentIA(String componentInstanceName, String providedServiceName) {
        String strFullProvidedServiceName = getFullProvidedServiceName(componentInstanceName, providedServiceName);
        if (m_oTransformedProvidedServices.containsKey(strFullProvidedServiceName)) {
            return m_oTransformedProvidedServices.get(strFullProvidedServiceName);
        }

        ComponentInstanceType oComponentInstance = m_oInputModel.getComponentInstance(componentInstanceName);
        ServiceImplementationType oServiceImplementation = m_oInputModel.getServiceImplementation(oComponentInstance.getForComponent(), providedServiceName);

        RMPIIAWrapper oInternalActivity = transformIntoEquivalentIA(componentInstanceName, oServiceImplementation);

        m_oTransformedProvidedServices.put(strFullProvidedServiceName, oInternalActivity);
        return oInternalActivity;
    }

    /**
     * Transform the call propagation into an equivalent internal activity.
     *
     * @param componentInstanceName the current component instance.
     * @param callPropagation       the call propagation.
     * @return the equivalent internal activity.
     */
    private RMPIIAWrapper transformIntoEquivalentIA(String componentInstanceName, CallPropagationType callPropagation) {
        RMPIIAWrapper oIAWrapper = null;

        if (callPropagation.isSetInternalActivity()) {
            oIAWrapper = new RMPIIAWrapper(m_oInputModel, callPropagation.getInternalActivity().getFailureModel());
        } else if (callPropagation.isSetCallingActivity()) {
            oIAWrapper = transformIntoEquivalentIA(componentInstanceName, callPropagation.getCallingActivity());
        } else if (callPropagation.isSetBranchingStructure()) {
            oIAWrapper = transformIntoEquivalentIA(componentInstanceName, callPropagation.getBranchingStructure());
        } else if (callPropagation.isSetLoopingStructure()) {
            oIAWrapper = transformIntoEquivalentIA(componentInstanceName, callPropagation.getLoopingStructure());
        } else if (callPropagation.isSetSequentialStructure()) {
            oIAWrapper = transformIntoEquivalentIA(componentInstanceName, callPropagation.getSequentialStructure());
        } else if (callPropagation.isSetParallelStructure()) {
            oIAWrapper = transformIntoEquivalentIA(componentInstanceName, callPropagation.getParallelStructure());
        } else if (callPropagation.isSetRetryStructure()) {
            oIAWrapper = transformIntoEquivalentIA(componentInstanceName, callPropagation.getRetryStructure());
        } else if (callPropagation.isSetMultiTryCatchStructure()) {
            oIAWrapper = transformIntoEquivalentIA(componentInstanceName, callPropagation.getMultiTryCatchStructure());
        } else if(callPropagation.isSetMVPStructure()){
            oIAWrapper = transformIntoEquivalentIA(componentInstanceName, callPropagation.getMVPStructure());
        }

        return oIAWrapper;
    }

    /**
     * Transform a MVP Structure into an equivalent internal activity.
     * @param componentInstanceName the current component instance name.
     * @param mvpStructure the MVP structure.
     * @return the equivalent internal activity.
     */
    private RMPIIAWrapper transformIntoEquivalentIA(String componentInstanceName, MVPStructureType mvpStructure) {
        int iMaxTI = mvpStructure.getMaxToleratedIncompletion();
        int minMS = mvpStructure.getMinMajoritySize();

        MVPStructureType.MVPPart[] oMVPParts = mvpStructure.getMVPPartArray();
        RMPIAgreementOfErrorsVectorWrapper oAgreementOfErrorsVectorWrapper = new RMPIAgreementOfErrorsVectorWrapper(mvpStructure.getAgreementOfErrorsVector(), minMS, oMVPParts.length);

        Set<Set<String>> oAIOS = m_oInputModel.getAIOS();

        RMPIIAWrapper oReturnIAWrapper = new RMPIIAWrapper(m_oInputModel, true);

        for (Set<String> oI : oAIOS) {
            //calculate all the probabilities of possibilities.
            MultiKeyMap oProbabilitiesOfPossibilities = new MultiKeyMap();
            oProbabilitiesOfPossibilities.put(0,0,0,1.0);

            RMPIErrorPropertyVectorWrapper oErrorPropertyVectorWrapper = null;
            for(int i=0; i<oMVPParts.length; i++){
                if(i < mvpStructure.sizeOfErrorPropertyVectorArray()){
                    oErrorPropertyVectorWrapper = new RMPIErrorPropertyVectorWrapper(mvpStructure.getErrorPropertyVectorArray(i));
                }

                oProbabilitiesOfPossibilities = calculateProbabilitiesOfPossibilities(oProbabilitiesOfPossibilities, transformIntoEquivalentIA(componentInstanceName, oMVPParts[i]), oI, oErrorPropertyVectorWrapper);
            }

            //voter
            double dProbOfCorOut = 0.0;
            double dProbOfErrOut = 0.0;
            Iterator oIterator = oProbabilitiesOfPossibilities.keySet().iterator();

            while(oIterator.hasNext()){
                MultiKey oPossibility = (MultiKey) oIterator.next();

                int iX = ((Integer)oPossibility.getKey(0)).intValue();
                int iY = ((Integer)oPossibility.getKey(1)).intValue();
                int iZ = ((Integer)oPossibility.getKey(2)).intValue();

                if(iZ<=iMaxTI && iX>=minMS){
                    dProbOfCorOut +=  ((Double) oProbabilitiesOfPossibilities.get(iX, iY, iZ)).doubleValue();
                }else if (iZ<=iMaxTI && iX<minMS && iY >= minMS ){
                    dProbOfErrOut +=  ((Double) oProbabilitiesOfPossibilities.get(iX, iY, iZ)).doubleValue() * oAgreementOfErrorsVectorWrapper.getAgreementOfErrors(iY);
                }
            }

            Set<String> oSignaledFailure = new HashSet<String>(Arrays.asList(new String[]{mvpStructure.getSignaledFailure().getInvolvedFailure()}));
            Set<String> oSetOfOutputFailures = new HashSet<String>();
            String[] oOutputFailures = StringUtils.split(mvpStructure.getErroneousOutput().getInvolvedFailures(), RMPI_COMMA);
            oSetOfOutputFailures.addAll(Arrays.asList(oOutputFailures));


            oReturnIAWrapper.setProbability(oI, RMPIInputModeler.RMPI_F0_AS_SET, dProbOfCorOut);
            oReturnIAWrapper.setProbability(oI, oSetOfOutputFailures,dProbOfErrOut);
            oReturnIAWrapper.setProbability(oI, oSignaledFailure, 1-dProbOfCorOut-dProbOfErrOut);
        }

        return oReturnIAWrapper;
    }



    /**
     * Calculate all the probabilities of possibilities after the execution of an MVPPart with the given input for the MVPPart.
     * @param probabilitiesOfPossibilities all the probabilities of possibilities before.
     * @param iaWrapper the equivalent IA of the MVPPart.
     * @param input the input for the MVPPart.
     * @param errorPropertyVectorWrapper the error property vector wrapper
     * @return  all the probabilities of possibilities after the execution of an MVPPart.
     */
    private MultiKeyMap calculateProbabilitiesOfPossibilities(MultiKeyMap probabilitiesOfPossibilities, RMPIIAWrapper iaWrapper, Set<String> input, RMPIErrorPropertyVectorWrapper errorPropertyVectorWrapper){
        Set<Set<String>> oAFS = m_oInputModel.getAFS();
        Set<Set<String>> oAIOS = m_oInputModel.getAIOS();

        MultiKeyMap oReturnMultiKeyMap = new MultiKeyMap();

        double dProbabilityOfIncreasingX = iaWrapper.getProbability(input, RMPIInputModeler.RMPI_F0_AS_SET);

        double dProbabilityOfIncreasingY = 0.0;
        double dProbabilityOfIncreasingZ = 0.0;

        for(Set<String> oO: oAIOS){
            if(oO.isEmpty()){
                continue;
            }

            dProbabilityOfIncreasingY+=iaWrapper.getProbability(input, oO)*(1-errorPropertyVectorWrapper.getProbability(oO));
            dProbabilityOfIncreasingZ+=iaWrapper.getProbability(input, oO)*errorPropertyVectorWrapper.getProbability(oO);
        }

        for(Set<String> oF: oAFS){
            dProbabilityOfIncreasingZ+=iaWrapper.getProbability(input, oF);
        }

        Iterator oIterator = probabilitiesOfPossibilities.keySet().iterator();
        while(oIterator.hasNext()){
            MultiKey oPossibility = (MultiKey) oIterator.next();

            int iX = ((Integer)oPossibility.getKey(0)).intValue();
            int iY = ((Integer)oPossibility.getKey(1)).intValue();
            int iZ = ((Integer)oPossibility.getKey(2)).intValue();

            double dProbability = ((Double) probabilitiesOfPossibilities.get(iX, iY, iZ)).doubleValue();
            if(oReturnMultiKeyMap.containsKey(iX+1,iY,iZ)){
                oReturnMultiKeyMap.put(iX+1,iY,iZ,((Double)oReturnMultiKeyMap.get(iX+1,iY,iZ)).doubleValue()+ dProbability *dProbabilityOfIncreasingX);
            }else{
                oReturnMultiKeyMap.put(iX+1,iY,iZ, dProbability *dProbabilityOfIncreasingX);
            }

            if(oReturnMultiKeyMap.containsKey(iX,iY+1,iZ)){
                oReturnMultiKeyMap.put(iX,iY+1,iZ,((Double)oReturnMultiKeyMap.get(iX,iY+1,iZ)).doubleValue()+ dProbability *dProbabilityOfIncreasingY);
            }else{
                oReturnMultiKeyMap.put(iX,iY+1,iZ, dProbability *dProbabilityOfIncreasingY);
            }

            if(oReturnMultiKeyMap.containsKey(iX,iY,iZ+1)){
                oReturnMultiKeyMap.put(iX,iY,iZ+1,((Double)oReturnMultiKeyMap.get(iX,iY,iZ+1)).doubleValue()+ dProbability *dProbabilityOfIncreasingZ);
            }else{
                oReturnMultiKeyMap.put(iX,iY,iZ+1, dProbability *dProbabilityOfIncreasingZ);
            }
        }

        return oReturnMultiKeyMap;
    }

    /**
     * Transform a calling activity into an equivalent internal activity.
     *
     * @param componentInstanceName the current component instance name.
     * @param callingActivity       a calling activity.
     * @return the equivalent internal activity.
     */
    private RMPIIAWrapper transformIntoEquivalentIA(String componentInstanceName, CallingActivityType callingActivity) {
        String strCalledService = callingActivity.getCalledService();

        ComponentConnectorType oComponentConnector = m_oInputModel.getComponentConnectorFor(componentInstanceName, strCalledService);
        String strFromComponentInstance = oComponentConnector.getFromService().getFromComponentInstance();
        String strFromProvidedServiceName = oComponentConnector.getFromService().getFromProvidedService();

        return transformIntoEquivalentIA(strFromComponentInstance, strFromProvidedServiceName);
    }

    /**
     * Transform a branching structure into an equivalent internal activity.
     *
     * @param componentInstanceName the current component instance name.
     * @param branchingStructure    a branching structure.
     * @return the equivalent internal activity.
     */
    private RMPIIAWrapper transformIntoEquivalentIA(String componentInstanceName, BranchingStructureType branchingStructure) {
        IfPart[] oIfParts = branchingStructure.getIfPartArray();

        RMPIIAWrapper[] oBranchingPartIAWrappers = new RMPIIAWrapper[oIfParts.length + 1];
        double[] oBranchingProbabilities = new double[oIfParts.length + 1];

        double dProbabilityOfAllIfBranches = 0.0;
        for (int i = 0; i < oIfParts.length; i++) {
            oBranchingPartIAWrappers[i] = transformIntoEquivalentIA(componentInstanceName, oIfParts[i]);

            double dProbability = m_oInputModel.getUsageProfilePartBCProbability(m_oUsageProfilePart, oIfParts[i].getBranchingCondition()).getValue();
            oBranchingProbabilities[i] = dProbability;
            dProbabilityOfAllIfBranches += dProbability;
        }

        if(branchingStructure.getElsePart() == null){
            oBranchingPartIAWrappers[oIfParts.length] = new RMPIIAWrapper(m_oInputModel, null);
        }else{
            oBranchingPartIAWrappers[oIfParts.length] = transformIntoEquivalentIA(componentInstanceName, branchingStructure.getElsePart());
        }

        oBranchingProbabilities[oIfParts.length] = 1 - dProbabilityOfAllIfBranches;

        return transformBranchesIntoEquivalentIA(oBranchingPartIAWrappers, oBranchingProbabilities);
    }

    /**
     * Transform branches with their branching probabilities into an equivalent internal activity.
     * @param oBranchingPartIAWrappers an array of branches (equivalent internal activities of branches).
     * @param oBranchingProbabilities an array of branching probabilities
     * @return the equivalent internal activity.
     */
    private RMPIIAWrapper transformBranchesIntoEquivalentIA(RMPIIAWrapper[] oBranchingPartIAWrappers, double[] oBranchingProbabilities) {
        Set<Set<String>> oAFS = m_oInputModel.getAFS();
        Set<Set<String>> oAIOS = m_oInputModel.getAIOS();

        RMPIIAWrapper oReturnIAWrapper = new RMPIIAWrapper(m_oInputModel, true);
        for (Set<String> oI : oAIOS) {
            for (Set<String> oF : oAFS) {
                double dResultProbability = 0.0;
                for (int i = 0; i < oBranchingPartIAWrappers.length; i++) {
                    dResultProbability += (oBranchingProbabilities[i] * oBranchingPartIAWrappers[i].getProbability(oI, oF));
                }

                oReturnIAWrapper.setProbability(oI, oF, dResultProbability);
            }

            for (Set<String> oO : oAIOS) {
                double dResultProbability = 0.0;
                for (int i = 0; i < oBranchingPartIAWrappers.length; i++) {
                    dResultProbability += (oBranchingProbabilities[i] * oBranchingPartIAWrappers[i].getProbability(oI, oO));
                }

                oReturnIAWrapper.setProbability(oI, oO, dResultProbability);
            }
        }

        return oReturnIAWrapper;
    }

    /**
     * Transform a looping structure into an equivalent internal activity.
     *
     * @param componentInstanceName the current component instance name.
     * @param loopingStructure      a looping structure.
     * @return the equivalent internal activity.
     */
    private RMPIIAWrapper transformIntoEquivalentIA(String componentInstanceName, LoopingStructureType loopingStructure) {
        RMPIIAWrapper oLoopingPartIAWrapper = transformIntoEquivalentIA(componentInstanceName, loopingStructure.getLoopingPart());

        UsageProfilePartType.LCAverage oLCAverage = m_oInputModel.getUsageProfilePartLCAverage(m_oUsageProfilePart, loopingStructure.getLoopCount());
        if(oLCAverage != null){
            if(loopingStructure.getLoopType() == LoopingStructureType.LoopType.SEQUENTIAL){
                return transformSimilarSequentialIAsIntoOneIA(oLoopingPartIAWrapper, oLCAverage.getValue());
            }else{
                return transformSimilarParallelIAsIntoOneIA(oLoopingPartIAWrapper, oLCAverage.getValue());
            }
        }else{
            UsageProfilePartType.LCProbabilityDistribution oLCProbabilityDistribution = m_oInputModel.getUsageProfilePartLCProbabilityDistribution(m_oUsageProfilePart, loopingStructure.getLoopCount());
            RMPILCProbabilityDistributionWrapper oLCDistributionWrapper = new RMPILCProbabilityDistributionWrapper(oLCProbabilityDistribution);
            Integer[] oLCValues = oLCDistributionWrapper.getLCValues();

            RMPIIAWrapper oBranchingPartIAWrappers[] = new RMPIIAWrapper[oLCValues.length];
            double[] oBranchingProbabilities = new double[oLCValues.length];

            for(int i=0; i< oLCValues.length; i++){
                if(loopingStructure.getLoopType() == LoopingStructureType.LoopType.SEQUENTIAL){
                    oBranchingPartIAWrappers[i] = transformSimilarSequentialIAsIntoOneIA(oLoopingPartIAWrapper, oLCValues[i]);
                }else{
                    oBranchingPartIAWrappers[i] = transformSimilarParallelIAsIntoOneIA(oLoopingPartIAWrapper, oLCValues[i]);
                }
                oBranchingProbabilities[i] =  oLCDistributionWrapper.getProbability(oLCValues[i]);
            }

            return transformBranchesIntoEquivalentIA(oBranchingPartIAWrappers,oBranchingProbabilities);
        }
    }

    /**
     * Transform n similar sequential internal activities into one internal activity.
     *
     * @param ia the similar internal activity.
     * @param n  the number of similar internal activities, should be greater than or equal 0.
     * @return the resulting internal activity.
     */
    private RMPIIAWrapper transformSimilarSequentialIAsIntoOneIA(RMPIIAWrapper ia, int n) {
        if (n == 0) {
            return new RMPIIAWrapper(m_oInputModel, false);
        } else if (n == 1) {
            return ia;
        } else if ((n % 2) == 0) {
            //n is even
            return transformSimilarSequentialIAsIntoOneIA(transformTwoSequentialIAsIntoOneIA(ia, ia), n / 2);
        } else {
            //n is odd
            return transformTwoSequentialIAsIntoOneIA(ia, transformSimilarSequentialIAsIntoOneIA(transformTwoSequentialIAsIntoOneIA(ia, ia), (n - 1) / 2));
        }
    }

    /**
     * Transform n similar parallel internal activities into one internal activity.
     *
     * @param ia the similar internal activity.
     * @param n  the number of similar internal activities, should be greater than or equal 0.
     * @return the resulting internal activity.
     */
    private RMPIIAWrapper transformSimilarParallelIAsIntoOneIA(RMPIIAWrapper ia, int n) {
        if (n == 0) {
            return new RMPIIAWrapper(m_oInputModel, false);
        } else if (n == 1) {
            return ia;
        } else if ((n % 2) == 0) {
            //n is even
            return transformSimilarParallelIAsIntoOneIA(transformTwoParallelIAsIntoOneIA(ia, ia), n / 2);
        } else {
            //n is odd
            return transformTwoParallelIAsIntoOneIA(ia, transformSimilarParallelIAsIntoOneIA(transformTwoParallelIAsIntoOneIA(ia, ia), (n - 1) / 2));
        }
    }

    /**
     * Transform two sequential internal activities into one internal activity.
     *
     * @param ia1 the first internal activity.
     * @param ia2 the second internal activity.
     * @return the resulting internal activity.
     */
    private RMPIIAWrapper transformTwoSequentialIAsIntoOneIA(RMPIIAWrapper ia1, RMPIIAWrapper ia2) {
        Set<Set<String>> oAFS = m_oInputModel.getAFS();
        Set<Set<String>> oAIOS = m_oInputModel.getAIOS();

        RMPIIAWrapper oResultIA = new RMPIIAWrapper(m_oInputModel, true);

        for (Set<String> oI : oAIOS) {
            for (Set<String> oF : oAFS) {
                double dResultProbability = 0.0;
                for (Set<String> oOPrime : oAIOS) {
                    dResultProbability += ia1.getProbability(oI, oOPrime) * ia2.getProbability(oOPrime, oF);
                }

                oResultIA.setProbability(oI, oF, ia1.getProbability(oI, oF) + dResultProbability);
            }

            for (Set<String> oO : oAIOS) {
                double dResultProbability = 0.0;
                for (Set<String> oOPrime : oAIOS) {
                    dResultProbability += ia1.getProbability(oI, oOPrime) * ia2.getProbability(oOPrime, oO);
                }

                oResultIA.setProbability(oI, oO, dResultProbability);
            }
        }

        return oResultIA;
    }

    /**
     * Transform a sequential structure into an equivalent internal activity.
     *
     * @param componentInstanceName the current component instance name.
     * @param sequentialStructure   a sequential structure.
     * @return the equivalent internal activity.
     */
    private RMPIIAWrapper transformIntoEquivalentIA(String componentInstanceName, SequentialStructureType sequentialStructure) {
        CallPropagationType[] oSequentialParts = sequentialStructure.getSequentialPartArray();

        RMPIIAWrapper oReturnIAWrapper = transformIntoEquivalentIA(componentInstanceName, oSequentialParts[0]);
        for (int i = 1; i < oSequentialParts.length; i++) {
            oReturnIAWrapper = transformTwoSequentialIAsIntoOneIA(oReturnIAWrapper, transformIntoEquivalentIA(componentInstanceName, oSequentialParts[i]));
        }

        return oReturnIAWrapper;
    }

    /**
     * Transform two parallel internal activities into one internal activity.
     *
     * @param ia1 the first internal activity.
     * @param ia2 the second internal activity.
     * @return the resulting internal activity.
     */
    private RMPIIAWrapper transformTwoParallelIAsIntoOneIA(RMPIIAWrapper ia1, RMPIIAWrapper ia2) {
        Set<Set<String>> oAFS = m_oInputModel.getAFS();
        Set<Set<String>> oAIOS = m_oInputModel.getAIOS();

        RMPIIAWrapper oResultIA = new RMPIIAWrapper(m_oInputModel, true);

        for (Set<String> oI : oAIOS) {
            //1
            oResultIA.setProbability(oI, RMPIInputModeler.RMPI_F0_AS_SET,
                    ia1.getProbability(oI, RMPIInputModeler.RMPI_F0_AS_SET) * ia2.getProbability(oI, RMPIInputModeler.RMPI_F0_AS_SET));

            //2
            double dTotalProbability = 0.0;
            for (Set<String> oO : oAIOS) {
                    dTotalProbability += ia1.getProbability(oI, oO);
            }

            for (Set<String> oF : oAFS) {
                oResultIA.setProbability(oI, oF, ia1.getProbability(oI, oF) + dTotalProbability * ia2.getProbability(oI, oF));
            }

            //3.1
            for (Set<String> oO1 : oAIOS) {
                if (oO1.isEmpty()) {
                    continue;
                }
                for (Set<String> oO2 : oAIOS) {
                    if (oO2.isEmpty()) {
                        continue;
                    }
                    Set<String> oUnionO1O2 = Sets.union(oO1, oO2);
                    oResultIA.setProbability(oI, oUnionO1O2,
                            oResultIA.getProbability(oI, oUnionO1O2)
                                    + ia1.getProbability(oI, oO1) * ia2.getProbability(oI, oO2));
                }
            }

            //3.2
            for (Set<String> oO : oAIOS) {
                if (oO.isEmpty()) {
                    continue;
                }

                oResultIA.setProbability(oI, oO,
                        oResultIA.getProbability(oI, oO)
                                + ia1.getProbability(oI, RMPIInputModeler.RMPI_F0_AS_SET) * ia2.getProbability(oI, oO)
                                + ia1.getProbability(oI, oO) * ia2.getProbability(oI, RMPIInputModeler.RMPI_F0_AS_SET));
            }

        }

        return oResultIA;
    }

    /**
     * Transform a parallel structure into an equivalent internal activity.
     *
     * @param componentInstanceName the current component instance name.
     * @param parallelStructure     a parallel structure.
     * @return the equivalent internal activity.
     */
    private RMPIIAWrapper transformIntoEquivalentIA(String componentInstanceName, ParallelStructureType parallelStructure) {
        CallPropagationType[] oParallelParts = parallelStructure.getParallelPartArray();
        RMPIIAWrapper oReturnIAWrapper = transformIntoEquivalentIA(componentInstanceName, oParallelParts[0]);
        for (int i = 1; i < oParallelParts.length; i++) {
            oReturnIAWrapper = transformTwoParallelIAsIntoOneIA(oReturnIAWrapper, transformIntoEquivalentIA(componentInstanceName, oParallelParts[i]));
        }

        return oReturnIAWrapper;
    }

    /**
     * Transform a retry structure into an equivalent internal activity.
     *
     * @param componentInstanceName the current component instance name.
     * @param retryStructure        a retry structure.
     * @return the equivalent internal activity.
     */
    private RMPIIAWrapper transformIntoEquivalentIA(String componentInstanceName, RetryStructureType retryStructure) {
        Set<Set<String>> oAFS = m_oInputModel.getAFS();
        Set<Set<String>> oAIOS = m_oInputModel.getAIOS();

        RMPIIAWrapper oRetryPartIAWrapper = transformIntoEquivalentIA(componentInstanceName, retryStructure.getRetryPart());

        int iRetryCount = retryStructure.getRetryCount();
        Set<Set<String>> oFHSet = getFHSet(retryStructure.getHandledFailures());

        RMPIIAWrapper oReturnIAWrapper = new RMPIIAWrapper(m_oInputModel, true);

        for (Set<String> oI : oAIOS) {
            RMPIMarkovModel oMarkovModel = new RMPIMarkovModel();
            for (int i = 0; i <= iRetryCount; i++) {
                //build Markov blocks
                String strRPi = "RP" + i;
                RMPIMarkovState oRPioIState = oMarkovModel.addState(strRPi, oI, false);

                for (Set<String> oF : oAFS) {
                    RMPIMarkovState oRPioFState = oMarkovModel.addState(strRPi, oF, false);
                    oMarkovModel.addTransition(oRPioIState, oRPioFState, oRetryPartIAWrapper.getProbability(oI, oF));
                }
            }

            // [F] states
            for (Set<String> oF : oAFS) {
                RMPIMarkovState oOFState = oMarkovModel.addState("", oF, true);

                //last retry
                oMarkovModel.addTransition("RP" + iRetryCount, oF, oOFState, 1.0);
            }

            // [O] states
            for (Set<String> oO : oAIOS) {
                RMPIMarkovState oOOState = oMarkovModel.addState("", oO, true);

                for (int i = 0; i <= iRetryCount; i++) {
                    oMarkovModel.addTransition("RP" + i, oI, oOOState, oRetryPartIAWrapper.getProbability(oI, oO));
                }
            }

            //
            for (int i = 0; i < iRetryCount; i++) {
                for (Set<String> oF : oAFS) {
                    RMPIMarkovState oRPioFState = oMarkovModel.getState("RP" + i, oF, false);
                    if (oFHSet.contains(oF)) {
                        oMarkovModel.addTransition(oRPioFState, "RP" + (i + 1), oI, false, 1.0);
                    } else {
                        oMarkovModel.addTransition(oRPioFState, "", oF, true, 1.0);
                    }
                }
            }

            //finally,
            RMPIMarkovState oRP0State = oMarkovModel.getState("RP0", oI, false);
            for (Set<String> oF : oAFS) {
                oReturnIAWrapper.setProbability(oI, oF, oMarkovModel.getProbability(oRP0State, "", oF));
            }

            for (Set<String> oO : oAIOS) {
                oReturnIAWrapper.setProbability(oI, oO, oMarkovModel.getProbability(oRP0State, "", oO));
            }
        }

        return oReturnIAWrapper;
    }

    /**
     * Transform a multi try catch structure into an equivalent internal activity.
     *
     * @param componentInstanceName  the current component instance name.
     * @param multiTryCatchStructure a multi try catch structure
     * @return the equivalent internal activity.
     */
    private RMPIIAWrapper transformIntoEquivalentIA(String componentInstanceName, MultiTryCatchStructureType multiTryCatchStructure) {
        Set<Set<String>> oAFS = m_oInputModel.getAFS();
        Set<Set<String>> oAIOS = m_oInputModel.getAIOS();

        MultiTryCatchPart[] oMultiTryCatchParts = multiTryCatchStructure.getMultiTryCatchPartArray();
        int iNumberOfInnerParts = oMultiTryCatchParts.length;

        RMPIIAWrapper[] oMPIAWrappers = new RMPIIAWrapper[iNumberOfInnerParts];
        List<Set<Set<String>>> oListOfFHSets = new ArrayList<Set<Set<String>>>();

        for (int i = 0; i < iNumberOfInnerParts; i++) {
            oMPIAWrappers[i] = transformIntoEquivalentIA(componentInstanceName, oMultiTryCatchParts[i]);
            oListOfFHSets.add(getFHSet(oMultiTryCatchParts[i].getHandledFailures()));
        }

        RMPIIAWrapper oReturnIAWrapper = new RMPIIAWrapper(m_oInputModel, true);

        for (Set<String> oI : oAIOS) {
            RMPIMarkovModel oMarkovModel = new RMPIMarkovModel();
            for (int i = 0; i < iNumberOfInnerParts; i++) {

                //build Markov blocks
                String strMPi = "MP" + i;
                RMPIMarkovState oMPioIState = oMarkovModel.addState(strMPi, oI, false);

                for (Set<String> oF : oAFS) {
                    RMPIMarkovState oMPiOFState = oMarkovModel.addState(strMPi, oF, false);
                    oMarkovModel.addTransition(oMPioIState, oMPiOFState, oMPIAWrappers[i].getProbability(oI, oF));
                }
            }

            // [F] states
            for (Set<String> oF : oAFS) {
                RMPIMarkovState oOFState = oMarkovModel.addState("", oF, true);

                //last part
                oMarkovModel.addTransition("MP" + (iNumberOfInnerParts - 1), oF, oOFState, 1.0);
            }

            // [O] states
            for (Set<String> oO : oAIOS) {
                RMPIMarkovState oOOState = oMarkovModel.addState("", oO, true);

                for (int i = 0; i < iNumberOfInnerParts; i++) {
                    oMarkovModel.addTransition("MP" + i, oI, oOOState, oMPIAWrappers[i].getProbability(oI, oO));
                }
            }

            //
            for (int i = 0; i < iNumberOfInnerParts - 1; i++) {
                for (Set<String> oF : oAFS) {
                    RMPIMarkovState oMPioFState = oMarkovModel.getState("MP" + i, oF, false);
                    int x = i + 1;
                    for (; x < iNumberOfInnerParts; x++) {
                        if (oListOfFHSets.get(x).contains(oF)) {
                            oMarkovModel.addTransition(oMPioFState, "MP" + (x), oI, false, 1.0);
                            break;
                        }
                    }

                    if (x == iNumberOfInnerParts) {
                        oMarkovModel.addTransition(oMPioFState, "", oF, true, 1.0);
                    }
                }

            }

            //finally,
            RMPIMarkovState oMP0State = oMarkovModel.getState("MP0", oI, false);
            for (Set<String> oF : oAFS) {
                oReturnIAWrapper.setProbability(oI, oF, oMarkovModel.getProbability(oMP0State, "", oF));
            }

            for (Set<String> oO : oAIOS) {
                oReturnIAWrapper.setProbability(oI, oO, oMarkovModel.getProbability(oMP0State, "", oO));
            }
        }

        return oReturnIAWrapper;
    }

    /**
     * Get the FH set from handled failures of a fault tolerance structure.
     *
     * @param handledFailures handled failures.
     * @return the FH set.
     */
    private Set<Set<String>> getFHSet(HandledFailuresType handledFailures) {
        String strHandledFailures = handledFailures.getStringValue();
        if (RMPI_NONE.equals(strHandledFailures)) {
            return new HashSet<Set<String>>();
        }

        if (RMPI_ALL.equals(strHandledFailures)) {
            return m_oInputModel.getAFS();
        }

        Set<Set<String>> oFHSet = new HashSet<Set<String>>();
        String[] oHandledFailures = StringUtils.split(strHandledFailures, RMPI_COMMA);
        for (String strHandledFailure : oHandledFailures) {
            Set<String> oSet = new HashSet<String>();
            oSet.add(strHandledFailure);
            oFHSet.add(oSet);
        }

        return oFHSet;
    }

    /**
     * Get the full provided service name.
     *
     * @param componentInstanceName component instance name.
     * @param providedServiceName   a provided service name.
     * @return the full provided service name.
     */
    private String getFullProvidedServiceName(String componentInstanceName, String providedServiceName) {
        return componentInstanceName + RMPI_DOT + providedServiceName;
    }
}
